-- Designed for speed and low garbage generation.
-- Does not deal with invalid patterns (e.g. [a-b-c]) or patterns with NULs.
-- Does not respect POSIX rules regarding files starting with dot
-- (dots at the beginning of the string to match are treated like any
-- other character; Lua patterns are not powerful enough to fully implement
-- these rules anyway, you need actual regular expressions.)

local function debugprt(line, idx, msg)
    -- Disable this function to disable tracing.
    io.stderr:write(line, '\n', string.rep(' ',idx-1), '^ ', msg, '\n')
end

local function glob_to_pattern(addr)
    local idx = 1
    debugprt(addr, idx, "starting")
    repeat
        idx = string.find(addr, '[[*.?%%^$()+-]', idx)
        if not idx then
            return string.format("^%s$", addr)
        end
        if addr:byte(idx) == 91 then -- [
            idx = idx + 1
            debugprt(addr, idx, "after opening [")
            if addr:byte(idx) == 33 then -- !
                -- replace [!...] with [^...]
                addr = string.format('%s^%s', addr:sub(1, idx-1), addr:sub(idx+1))
                -- fall through to skip it
                debugprt(addr, idx, "after replacing [! with [^")
            end
            if addr:byte(idx) == 94 then -- ^
                idx = idx + 1 -- skip initial ^
                debugprt(addr, idx, "after skipping [^")
            end
            if addr:byte(idx) == 93 then -- ]
                idx = idx + 1 -- skips the first ] if present
                debugprt(addr, idx, "after skipping ] in []...]")
            end
            local startset = idx
            repeat
                idx = string.find(addr, '[]%%]', idx)
                if not idx then
                    error("Unclosed [ in pattern: " .. addr)
                end
                if addr:byte(idx) == 37 then -- %
                    if addr:byte(idx-1) == 45 and startset ~= idx - 1 then -- end of range
                        -- manual says that %% at the beginning or end of a range makes no sense
                        -- convert x-% to x-$%% (take % out of the range)
                        addr = string.format('%s$%%%s', addr:sub(1, idx-1), addr:sub(idx))
                        idx = idx + 3
                        debugprt(addr, idx, "after replacing [...x-%...] with [...x-$%%...]")
                    elseif addr:byte(idx+1) == 45 and addr:byte(idx+2) ~= 93 then -- start of range
                        -- manual says that %% at the beginning or end of a range makes no sense
                        -- convert %-x to %%&-x (take % out of the range)
                        addr = string.format('%s%%&%s', addr:sub(1, idx), addr:sub(idx+1))
                        idx = idx + 3
                        debugprt(addr, idx, "after replacing [...%-x...] with [...%%&-x...]")
                    else
                        -- normal %, prefix with another %
                        addr = string.format('%s%%%s', addr:sub(1, idx), addr:sub(idx+1))
                        idx = idx + 2
                        debugprt(addr, idx, "after adding % in [...%...]")
                    end
                else
                    break
                end
            until false
            idx = idx + 1
            debugprt(addr, idx, "after closing [...]")
        elseif addr:byte(idx) == 42 then -- *
            addr = string.format('%s.*%s', addr:sub(1, idx-1), addr:sub(idx+1))
            idx = idx + 2
            debugprt(addr, idx, "after replacing * with .*")
        elseif addr:byte(idx) == 63 then -- ?
            addr = string.format('%s.%s', addr:sub(1, idx-1), addr:sub(idx+1))
            idx = idx + 1
            debugprt(addr, idx, "after replacing ? with .")
        else
            addr = string.format('%s%%%s', addr:sub(1, idx-1), addr:sub(idx))
            idx = idx + 2
            debugprt(addr, idx, "after adding %")
        end
    until false
end

print(glob_to_pattern('a?b.c*d?e^%$f[3?*4.%] -+()[!x] [^][] [][] ["-%-] [-%] [-%-x]'))